/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * Main.java
 *
 * Created on 04.05.2011, 16:10:31
 */
package ch.flagmansworld;

import java.awt.Cursor;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Scanner;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.table.DefaultTableModel;
import org.apache.log4j.Logger;

/**
 * Application to install or uninstall APK's
 * @author Patrick Wachsmuth
 * @version 1.2
 */
public class Main extends javax.swing.JFrame {

    private static Logger logger = Logger.getLogger(Main.class);
    private static final String version = "1.2";
    private File adb;
    private File aapt;
    private OptionObj config = new OptionObj();

    /** Creates new form Main */
    public Main() {
        initComponents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jTextField_Adb = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        jTextField_App = new javax.swing.JTextField();
        jButton_Adb = new javax.swing.JButton();
        jButton_App = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        jXTable_Data = new org.jdesktop.swingx.JXTable();
        jButton_Sync = new javax.swing.JButton();
        jButton_Install = new javax.swing.JButton();
        jCheckBox_SystemApps = new javax.swing.JCheckBox();
        jButton_Uninstall = new javax.swing.JButton();
        jButton_Download = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
            public void windowOpened(java.awt.event.WindowEvent evt) {
                formWindowOpened(evt);
            }
        });

        jLabel1.setText("Adb/Aapt Pfad:");

        jTextField_Adb.setToolTipText("{Android_SDK}\\platform-tools\\");

            jLabel2.setText("App Pfad:");

            jTextField_App.setToolTipText("Path to the APK's");

            jButton_Adb.setText("...");
            jButton_Adb.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_AdbActionPerformed(evt);
                }
            });

            jButton_App.setText("...");
            jButton_App.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_AppActionPerformed(evt);
                }
            });

            jXTable_Data.setModel(new javax.swing.table.DefaultTableModel(
                new Object [][] {

                },
                new String [] {
                    "Name", "Package", "Select", "Source"
                }
            ) {
                Class[] types = new Class [] {
                    java.lang.Object.class, java.lang.Object.class, java.lang.Boolean.class, java.lang.Object.class
                };

                public Class getColumnClass(int columnIndex) {
                    return types [columnIndex];
                }
            });
            jScrollPane1.setViewportView(jXTable_Data);

            jButton_Sync.setText("Collect Data");
            jButton_Sync.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_SyncActionPerformed(evt);
                }
            });

            jButton_Install.setText("Install");
            jButton_Install.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_InstallActionPerformed(evt);
                }
            });

            jCheckBox_SystemApps.setText("Show System-Apps");
            jCheckBox_SystemApps.setToolTipText("If checked, you will also see the App's under /system (Need root!!!)");

            jButton_Uninstall.setText("Uninstall");
            jButton_Uninstall.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_UninstallActionPerformed(evt);
                }
            });

            jButton_Download.setText("Download from Phone");
            jButton_Download.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton_DownloadActionPerformed(evt);
                }
            });

            org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
            getContentPane().setLayout(layout);
            layout.setHorizontalGroup(
                layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                    .addContainerGap()
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                        .add(org.jdesktop.layout.GroupLayout.LEADING, jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 776, Short.MAX_VALUE)
                        .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup()
                            .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                                .add(jLabel1)
                                .add(jLabel2))
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                            .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                                .add(org.jdesktop.layout.GroupLayout.TRAILING, jTextField_App, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 646, Short.MAX_VALUE)
                                .add(org.jdesktop.layout.GroupLayout.TRAILING, jTextField_Adb, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 646, Short.MAX_VALUE))
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                            .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                                .add(org.jdesktop.layout.GroupLayout.TRAILING, jButton_Adb)
                                .add(org.jdesktop.layout.GroupLayout.TRAILING, jButton_App)))
                        .add(layout.createSequentialGroup()
                            .add(jCheckBox_SystemApps)
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 279, Short.MAX_VALUE)
                            .add(jButton_Sync)
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                            .add(jButton_Install)
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                            .add(jButton_Uninstall)
                            .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                            .add(jButton_Download)))
                    .addContainerGap())
            );
            layout.setVerticalGroup(
                layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                .add(layout.createSequentialGroup()
                    .addContainerGap()
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                        .add(jLabel1)
                        .add(jTextField_Adb, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .add(jButton_Adb))
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                        .add(jLabel2)
                        .add(jButton_App)
                        .add(jTextField_App, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                    .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 401, Short.MAX_VALUE)
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                        .add(jCheckBox_SystemApps)
                        .add(jButton_Download)
                        .add(jButton_Uninstall)
                        .add(jButton_Install)
                        .add(jButton_Sync))
                    .addContainerGap())
            );

            pack();
        }// </editor-fold>//GEN-END:initComponents

    /**
     * Call Sync-Method
     * @param evt 
     */
    private void jButton_SyncActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_SyncActionPerformed
        try {
            this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            Sync();
            this.setCursor(Cursor.getDefaultCursor());
        } catch (IOException | NoSuchElementException e) {
            logger.error(e);
            this.setCursor(Cursor.getDefaultCursor());
        }
    }//GEN-LAST:event_jButton_SyncActionPerformed

    /**
     * Set the Form-Title, Read the Configuration-File and show the Info-Dialog
     * @param evt 
     */
    private void formWindowOpened(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowOpened
        this.setTitle("AppInstaller v" + version);
        this.setLocationRelativeTo(null);

        try {
            readConfig();

            if (config.isShowInfo()) {
                InfoDialog info = new InfoDialog(this, true);
                info.setVariables(config);
                info.setVisible(true);
            }

            jTextField_Adb.setText(config.getAdbPath());
            jTextField_App.setText(config.getAppPath());

        } catch (IOException e) {
            logger.error(e);
        }
    }//GEN-LAST:event_formWindowOpened

    /**
     * Open the FileChooser-Dialog to select the Adb-Path
     * @param evt 
     */
    private void jButton_AdbActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_AdbActionPerformed
        JFileChooser fc = new JFileChooser();

        fc.setSelectedFile(new File(jTextField_Adb.getText()));
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int state = fc.showOpenDialog(null);

        if (state == JFileChooser.APPROVE_OPTION) {
            jTextField_Adb.setText(fc.getSelectedFile().getAbsolutePath());
        }
    }//GEN-LAST:event_jButton_AdbActionPerformed

    /**
     * Open the FileChooser-Dialog to select the App-Path
     * @param evt 
     */
    private void jButton_AppActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_AppActionPerformed
        JFileChooser fc = new JFileChooser();

        fc.setSelectedFile(new File(jTextField_App.getText()));
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int state = fc.showOpenDialog(null);

        if (state == JFileChooser.APPROVE_OPTION) {
            jTextField_App.setText(fc.getSelectedFile().getAbsolutePath());
        }
    }//GEN-LAST:event_jButton_AppActionPerformed

    /**
     * Checks the Paths, call installApps-, and Sync-Method
     * @param evt 
     */
    private void jButton_InstallActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_InstallActionPerformed
        try {
            if (!checkAdbPath()) {
                JOptionPane.showMessageDialog(null, "Adb/Aapt-Path not defined or Android SDk is not installed", "Error", JOptionPane.ERROR_MESSAGE);
            } else if (jTextField_App.getText().isEmpty()) {
                JOptionPane.showMessageDialog(null, "App-Path not defined", "Error", JOptionPane.ERROR_MESSAGE);
            } else {
                this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                installApps();
                Sync();
                this.setCursor(Cursor.getDefaultCursor());
            }
        } catch (IOException | NoSuchElementException e) {
            logger.error(e);
            this.setCursor(Cursor.getDefaultCursor());
        }
    }//GEN-LAST:event_jButton_InstallActionPerformed

    /**
     * Checks the Paths, call uninstallApps- and Sync-Method
     * @param evt 
     */
    private void jButton_UninstallActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_UninstallActionPerformed
        try {
            if (!checkAdbPath()) {
                JOptionPane.showMessageDialog(null, "Adb/Aapt-Path not defined or Android SDk is not installed", "Error", JOptionPane.ERROR_MESSAGE);
            } else if (jTextField_App.getText().isEmpty()) {
                JOptionPane.showMessageDialog(null, "App-Path not defined", "Error", JOptionPane.ERROR_MESSAGE);
            } else {
                this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                uninstallApps();
                Sync();
                this.setCursor(Cursor.getDefaultCursor());
            }
        } catch (IOException | NoSuchElementException e) {
            logger.error(e);
            this.setCursor(Cursor.getDefaultCursor());
        }
    }//GEN-LAST:event_jButton_UninstallActionPerformed

    /**
     * Checks the Paths, then Download the selected APK's to the App-Path and calls the Sync-Method
     * @param evt 
     */
    private void jButton_DownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton_DownloadActionPerformed
        try {
            if (!checkAdbPath()) {
                JOptionPane.showMessageDialog(null, "Adb/Aapt-Path not defined or Android SDk is not installed", "Error", JOptionPane.ERROR_MESSAGE);
            } else if (jTextField_App.getText().isEmpty()) {
                JOptionPane.showMessageDialog(null, "App-Path not defined", "Error", JOptionPane.ERROR_MESSAGE);
            } else {
                this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                downloadApp();
                Sync();
                this.setCursor(Cursor.getDefaultCursor());
            }
        } catch (IOException | NoSuchElementException | InterruptedException e) {
            logger.error(e);
            this.setCursor(Cursor.getDefaultCursor());
        }
    }//GEN-LAST:event_jButton_DownloadActionPerformed

    /**
     * Saves the Path's to the Configuration-File
     * @param evt 
     */
    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        try {
            readConfig();

            Properties conf = new Properties();
            conf.setProperty("adb.path", jTextField_Adb.getText());
            conf.setProperty("app.path", jTextField_App.getText());
            conf.setProperty("startup.dialog", String.valueOf(config.isShowInfo()));

            File configuration = new File("config.properties");
            conf.store(new FileOutputStream(configuration), "App-Configuration");
        } catch (IOException e) {
            logger.error(e);
        }
    }//GEN-LAST:event_formWindowClosing

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                new Main().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButton_Adb;
    private javax.swing.JButton jButton_App;
    private javax.swing.JButton jButton_Download;
    private javax.swing.JButton jButton_Install;
    private javax.swing.JButton jButton_Sync;
    private javax.swing.JButton jButton_Uninstall;
    private javax.swing.JCheckBox jCheckBox_SystemApps;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextField jTextField_Adb;
    private javax.swing.JTextField jTextField_App;
    private org.jdesktop.swingx.JXTable jXTable_Data;
    // End of variables declaration//GEN-END:variables

    /**
     * Get all Apps on Device
     * @return deviceApps
     * @throws IOException 
     */
    private SearchList getDeviceApps() throws IOException, NoSuchElementException {
        SearchList deviceApps = new SearchList();
        ProcessBuilder builder = new ProcessBuilder("cmd", "/c", adb.getAbsolutePath() + " shell pm list packages -f");

        Process p = builder.start();

        Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\Z");
        String response = s.next();
        String[] split = response.split("package:");

        for (int i = 0; i < split.length; i++) {
            String[] packageArray = split[i].split("=");

            if (!split[i].equals("")) {
                packageArray[1] = packageArray[1].replaceAll("\r\r\n", "");
                if (jCheckBox_SystemApps.isSelected()) {
                    deviceApps.add(new AppObj(packageArray[0], packageArray[1], false, "remote"));
                } else {
                    if (!packageArray[0].contains("/system/")) {
                        deviceApps.add(new AppObj(packageArray[0], packageArray[1], false, "remote"));
                    }
                }
            }
        }

        return deviceApps;
    }

    /**
     * Get all local Apps
     * @return localApps
     * @throws IOException 
     */
    private SearchList getLocalApps() throws IOException, NoSuchElementException {
        SearchList localApps = new SearchList();
        File dir = new File(jTextField_App.getText());

        File apps[] = dir.listFiles(new FileFilter() {

            @Override
            public boolean accept(File f) {
                return f.getName().toLowerCase().endsWith(".apk")
                        || f.getName().toLowerCase().endsWith(".APK");
            }
        });

        for (int i = 0; i < apps.length; i++) {
            ProcessBuilder builder = new ProcessBuilder("cmd", "/c", aapt.getAbsolutePath() + " dump badging " + apps[i].getAbsolutePath());

            Process p = builder.start();

            Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\Z");
            String response = s.next();
            int start = response.indexOf("name='");
            int end = response.indexOf("'", start + 6);
            String pkg = response.substring(start + 6, end);
            localApps.add(new AppObj(apps[i].getName(), pkg, false, "local"));
        }

        return localApps;
    }

    /**
     * Merge the devicesApps and localApps
     * @param deviceApps
     * @param localApps
     * @return allApps
     */
    private SearchList mergeApps(SearchList deviceApps, SearchList localApps) {
        SearchList allApps = new SearchList();

        for (int i = 0; i < deviceApps.size(); i++) {
            if (localApps.searchName(deviceApps.get(i).getApkPackage())) {
                allApps.add(new AppObj(deviceApps.get(i).getName(), deviceApps.get(i).getApkPackage(), deviceApps.get(i).isInstalled(), "local/remote"));
            } else {
                allApps.add(deviceApps.get(i));
            }
        }

        for (int i = 0; i < localApps.size(); i++) {
            if (!allApps.searchName(localApps.get(i).getApkPackage())) {
                allApps.add(localApps.get(i));
            }
        }

        return allApps;
    }

    /**
     * Installs the selected Apps on the Device
     * @throws IOException 
     */
    private void installApps() throws IOException {
        for (int i = 0; i < jXTable_Data.getRowCount(); i++) {
            if (jXTable_Data.getValueAt(i, 3).equals("local") && jXTable_Data.getValueAt(i, 2).equals(true)) {
                ProcessBuilder builder = new ProcessBuilder("cmd", "/c", adb.getAbsolutePath() + " install " + jTextField_App.getText() + "\\" + jXTable_Data.getValueAt(i, 0));

                Process p = builder.start();

                Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\Z");
                System.out.println(s.next());
            }
        }
    }

    /**
     * Uninstalls the selected Apps on the Device
     * @throws IOException 
     */
    private void uninstallApps() throws IOException {
        for (int i = 0; i < jXTable_Data.getRowCount(); i++) {
            if (jXTable_Data.getValueAt(i, 3).equals("remote") && jXTable_Data.getValueAt(i, 2).equals(true) || jXTable_Data.getValueAt(i, 3).equals("local/remote") && jXTable_Data.getValueAt(i, 2).equals(true)) {
                ProcessBuilder builder = new ProcessBuilder("cmd", "/c", adb.getAbsolutePath() + " uninstall " + jXTable_Data.getValueAt(i, 1));
                Process p = builder.start();

                Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\Z");
                System.out.println(s.next());
            }
        }
    }

    /**
     * Downlaods the selected Apps from Phone
     * @throws IOException
     * @throws InterruptedException 
     */
    private void downloadApp() throws IOException, InterruptedException {
        for (int i = 0; i < jXTable_Data.getRowCount(); i++) {
            if (jXTable_Data.getValueAt(i, 3).equals("remote") && jXTable_Data.getValueAt(i, 2).equals(true)) {
                ProcessBuilder builder = new ProcessBuilder("cmd", "/c", adb.getAbsolutePath() + " pull " + jXTable_Data.getValueAt(i, 0));
                builder.directory(new File(jTextField_App.getText()));

                Process p = builder.start();
                p.waitFor();
            }
        }
    }

    /**
     * Calls getDeviceApps-, getLocalApps-, mergeApps- and FillTable-Method
     * @throws IOException 
     */
    private void Sync() throws IOException, NoSuchElementException {
        if (!checkAdbPath() || jTextField_App.getText().isEmpty()) {
            JOptionPane.showMessageDialog(null, "Adb/Aapt- or App-Path not defined", "Error", JOptionPane.ERROR_MESSAGE);
        } else {
            SearchList deviceApps = getDeviceApps();
            SearchList localApps = getLocalApps();
            SearchList allApps = mergeApps(deviceApps, localApps);
            FillTable(allApps);
        }
    }

    /**
     * Fill the table with the AppObj's
     * @param allApps 
     */
    private void FillTable(SearchList allApps) {
        DefaultTableModel model = (DefaultTableModel) jXTable_Data.getModel();
        model.setRowCount(0);
        int counter = 0;

        for (int i = 0; i < allApps.size(); i++) {
            model.addRow(new Vector());
            model.setValueAt(allApps.get(i).getName(), counter, 0);
            model.setValueAt(allApps.get(i).getApkPackage(), counter, 1);
            model.setValueAt(allApps.get(i).isInstalled(), counter, 2);
            model.setValueAt(allApps.get(i).getSource(), counter, 3);
            counter++;
        }

        jXTable_Data.packAll();
    }

    /**
     * Check if the adb.exe and aapt.exe in the selected Adb-Path
     * @return exist
     */
    private boolean checkAdbPath() {
        boolean exist = false;

        adb = new File(jTextField_Adb.getText() + "\\adb.exe");
        aapt = new File(jTextField_Adb.getText() + "\\aapt.exe");

        if (adb.exists() && aapt.exists()) {
            exist = true;
        }

        return exist;
    }

    /**
     * Reads the Configuration-File
     * @throws IOException 
     */
    private void readConfig() throws IOException {
        Properties properties = new Properties();

        try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream("./config.properties"))) {
            properties.load(stream);
        }
        config.setAdbPath(properties.getProperty("adb.path"));
        config.setAppPath(properties.getProperty("app.path"));
        config.setShowInfo(Boolean.parseBoolean(properties.getProperty("startup.dialog")));
    }
}
